package com.junlinpro.udp;


import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.junlinpro.config.ApplicationContextProvider;
import com.junlinpro.entity.MessageEntity;
import com.junlinpro.service.TimingAudioService;
import com.junlinpro.utils.BufferMemory4G;
import com.junlinpro.utils.ByteUtil;
import com.junlinpro.utils.CardStateUtil;
import com.junlinpro.utils.Constant;
import com.junlinpro.utils.CrcUtils;
import com.junlinpro.utils.DateUtil;
import com.junlinpro.utils.MessageUtil;
import com.junlinpro.utils.SocketChannelMap;
import com.junlinpro.utils.UdpMapData;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UdpServerHandler extends ChannelHandlerAdapter {

    // 采集卡状态管理类（储存到redis中的）
    private CardStateUtil cardStateUtil;
    private TimingAudioService timingAudioService;
    private UdpMapData udpMapData;
    private BufferMemory4G bufferMemory4G;

    public UdpServerHandler() {
        this.timingAudioService = ApplicationContextProvider.getBean(TimingAudioService.class);
        this.cardStateUtil = ApplicationContextProvider.getBean(CardStateUtil.class);
        this.udpMapData = ApplicationContextProvider.getBean(UdpMapData.class);
        this.bufferMemory4G = ApplicationContextProvider.getBean(BufferMemory4G.class);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

        try {
            DatagramPacket datagramPacket = (DatagramPacket) msg;
            // 将数据转换为Byte数组
            ByteBuf myBuf = datagramPacket.content();
            byte[] msgByteArray = new byte[myBuf.readableBytes()];
            myBuf.readBytes(msgByteArray);

            if (myBuf != null) {
                ReferenceCountUtil.release(myBuf);
                String clientIP = ctx.channel().id().toString();
                // 将数据放入缓存中
                bufferMemory4G.udpAdd(clientIP, msgByteArray);
                // 从缓存中读取
                List<byte[]> memoryDataList = bufferMemory4G.udpGetData(clientIP);
                if (memoryDataList != null && memoryDataList.size() > 0) {
                    for (int g = 0; g < memoryDataList.size(); g++) {
                        // 声明一个ByteBuf
                        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
                        buf.writeBytes(memoryDataList.get(g));

                        int msgLen = buf.readableBytes() - 4;
                        byte[] crcData = new byte[msgLen];
                        buf.getBytes(0, crcData);
                        byte[] crcValue = new byte[2];
                        buf.getBytes(msgLen, crcValue, 0, 2);
                        byte[] crcResult = CrcUtils.getCRCValue(crcData);
                        // 进行crc校验
                        if (crcValue[0] == crcResult[0] && crcValue[1] == crcResult[1]) {
                            // 对帧数据进行解析
                            MessageEntity entity = MessageUtil.newBufToMessage(buf);
                            ReferenceCountUtil.release(buf);
                            // 获得通道号
                            byte channelId = entity.getChannelId();
                            // 采集卡类型
                            byte credType = entity.getCardType();
                            // 获取帧序号
                            int frameId = entity.getFrameId();
                            // 获得硬件上报的采集卡编号
                            int cardId = entity.getCardId();

                            //根据帧序号来判断是否需要将链接对象更新到存储的内存中
                            //因为需要每分钟发送心跳给嵌入式所以需要将链接对象存储起来
                            //判断如果是第一帧的和375倍数帧的话就将链接实例储存和更新硬件的在线状态(因为每帧的间隔是80毫秒，所以375帧就是30秒。30秒做一次操作)
                            if (frameId == 0 || frameId % 375 == 0) {

                                // 储存链接对象，用于发送心跳使用
                                //SocketChannelMap.heartbeatChannelMapAdd(String.valueOf(cardId), ctx.channel());

                                // 储存采集卡状态信息
                                cardStateUtil.cardStateAdd(String.valueOf(cardId));
                            }
                            // 判断是否为手持设备传输的数据(0采集卡，1手持)
                            if (credType == 1) {
                            	//不支持手持设备 xiazq 20211016
                            	log.info("---手持的设备  暂不支持---");
                               } else {
                                // 定时上报数据解析和定时结束帧
                                // 该类型上报的数据为一帧解析4次，并且上报的通道号为“2”表示为组合通道
                                if (entity.getCmd() == Constant.CMD_AUDIO_DATA || entity.getCmd() == Constant.CMD_AUDIO_DATA_END) {
                                    //实时上传的数据
                                    if (entity.getCmd() == Constant.CMD_AUDIO_DATA) {
                                    	if (channelId == 80) {
                                    		log.info("-- 双通道上报 --");
                                    	}                                            
                                        // 取出音频数据
                                        byte[] frameByte = entity.getData();
                                        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
                                        byteBuf.writeBytes(frameByte);
                                        // 跳过12个字节，这12个字节在手持设备使用在此无用
                                        byteBuf.readBytes(12);
                                        // 解压后的“0”通道数据
                                        List<Byte> zeroByteList = new ArrayList<>();
                                        // 当前是单通道进行解析
                                        for (int i = 0; i < 2; i++) {
                                            byteBuf.skipBytes(4);
                                            byte[] byteLen = new byte[4];
                                            byteBuf.readBytes(byteLen);
                                            int len = ByteUtil.byteArrayToInt(byteLen);
                                            byte[] firstFrame = new byte[len];
                                            byteBuf.readBytes(firstFrame);
                                            // 处理前两针数据
                                            if (i < 2) {
                                                // 将解压后的值放入集合中
                                                for (int j = 0; j < firstFrame.length; j++) {
                                                    zeroByteList.add(firstFrame[j]);
                                                }
                                                // 说明已经收到两包数据
                                                if (i == 1) {
                                                    String cardKey = entity.getCardId() + "_" + entity.getChannelId() + entity.getOccurTime();
                                                    // 储存该帧上报的次数
                                                    udpMapData.frameNumerMapPut(cardKey);
                                                    byte[] bufferAll = new byte[zeroByteList.size()];
                                                    // 将值储存到临时变量
                                                    for (int j = 0; j < zeroByteList.size(); j++) {
                                                        bufferAll[j] = zeroByteList.get(j);
                                                    }
                                                    // 帧序号
                                                    String frameKey = String.valueOf(entity.getFrameId());
                                                    // 将值放入操作变量中
                                                    udpMapData.udpMapPut(cardKey, frameKey, bufferAll);
                                                }
                                            }
                                        }
                                        ReferenceCountUtil.release(byteBuf);
                                        
                                    } else if (entity.getCmd() == Constant.CMD_AUDIO_DATA_END) { // 如果为结束帧进行识别调用
                                    	log.info("---收到定时上报结束帧---");
                                        String cardKey = entity.getCardId() + "_" + entity.getChannelId() + entity.getOccurTime();
                                        // 判断收到的包数是否足够
                                        if (udpMapData.frameNumerMapGet(cardKey) >= Constant.TIMING_MIN_FRAMENUMER) {
                                        	
                                        	// 收到结束帧后给嵌入式回复
                                            MessageEntity rtnMsg = new MessageEntity();
                                            rtnMsg.setCardId(entity.getCardId());
                                            rtnMsg.setChannelId(entity.getChannelId());
                                            byte cmd = 0x10;
                                            rtnMsg.setVersionNum(entity.getVersionNum());
                                            rtnMsg.setCmd(cmd);
                                            // 帧序号
                                            rtnMsg.setFrameId(0);
                                            // 数据总长度
                                            rtnMsg.setAllLen(23);
                                            // 类型
                                            rtnMsg.setCardType(entity.getCardType());
                                            // UTC时间
                                            // 转换成时间戳
                                            int utcTimeStamp = DateUtil.transForMilliSecond(new Date());
                                            rtnMsg.setOccurTime(utcTimeStamp);

                                            byte[] rtnByteMsg = MessageUtil.rtnMessageTobyte(rtnMsg);
                                            Channel channel = SocketChannelMap.get(String.valueOf(entity.getCardId()));
                                            if(channel!=null) {
                                            	log.info("收到定时上报结束指令，上位机回复采集卡"+entity.getCardId());
                                            	channel.writeAndFlush(Unpooled.copiedBuffer(rtnByteMsg));
                                            }else {
                                            	log.info("收到定时上报结束指令，采集卡"+entity.getCardId()+" 的 channel is null");
                                            }
                                            
                                            
                                            // 处理完成对帧次数进行置空处理
                                            udpMapData.frameNumerSetNull(cardKey);
                                            // 判断数据是否为重复数据
                                            boolean repeatData = timingAudioService.dataIsAdd(entity.getCardId() + "_" + entity.getChannelId(), String.valueOf(entity.getOccurTime()), 1);
                                            // 不为重复数据才可添加
                                            if (repeatData) {
                                                // 调用储存及识别接口
                                                timingAudioService.recognitionAndpersistence(cardKey, String.valueOf(entity.getCardId()), String.valueOf(entity.getChannelId()),entity.getCardType(),2, String.valueOf(entity.getOccurTime()));
                                            }
                                                                                       

                                        } else {
                                            // 获得一下数据，以清除内存
                                            udpMapData.udpMapGet(cardKey);
                                            log.info("--- 定时上传数据，总包数未超过 95% ---");
                                        }

                                    }
                                } else if (entity.getCmd() == Constant.CMD_FRONT_ALARM || entity.getCmd() == Constant.CMD_FRONT_ALARM_END) {  // 报警逻辑处理
                                    // 报警数据处理逻辑  目前君林采集卡不支持此功能 xiazq 20211016
                                    if (entity.getCmd() == Constant.CMD_FRONT_ALARM) {
                                        // 报警发送上来的数据有时为单通道有时为双通道，判断通道号为“2”的话说明为双通道
                                        if (channelId == 80) { // 双通道逻辑，前两次为“0”通道，后两次为“1”通道
                                        	log.info("--- 双通道报警 ---");
                                            byte[] frameByte = entity.getData();

                                            ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
                                            byteBuf.writeBytes(frameByte);

                                            // 跳过12个字节，这12个字节在手持设备使用在此无用
                                            byteBuf.readBytes(12);

                                            // 储存解压后的全部数据
                                            List<Byte> byteList = new ArrayList<>();
                                            List<Byte> byteListTwo = new ArrayList<>();

                                            for (int i = 0; i < 4; i++) {
                                                byteBuf.skipBytes(4);
                                                byte[] byteLen = new byte[4];
                                                byteBuf.readBytes(byteLen);
                                                int len = ByteUtil.byteArrayToInt(byteLen);
                                                byte[] firstFrame = new byte[len];
                                                byteBuf.readBytes(firstFrame);

                                                // 判断前两包是0通道数据后两包是1通道数据
                                                if (i < 2) {
                                                    if (firstFrame != null && firstFrame.length > 0) {
                                                        for (int j = 0; j < firstFrame.length; j++) {
                                                            byteList.add(firstFrame[j]);
                                                        }
                                                    }
                                                    // 说明已经收到两包数据
                                                    if (i == 1) {
                                                        String cardKey = entity.getCardId() + "_0" + entity.getOccurTime();
                                                        // 储存该帧上报的次数
                                                        udpMapData.alarmFrameNumerMapPut(cardKey);
                                                        byte[] bufferAll = new byte[byteList.size()];
                                                        // 将值储存到临时变量
                                                        for (int j = 0; j < byteList.size(); j++) {
                                                            bufferAll[j] = byteList.get(j);
                                                        }
                                                        // 报警帧次数
                                                        String frameKey = String.valueOf(entity.getFrameId());
                                                        // 将值放入操作变量中
                                                        udpMapData.alarmUdpMapPut(cardKey, frameKey, bufferAll);

                                                    }

                                                } else {
                                                    if (firstFrame != null && firstFrame.length > 0) {
                                                        for (int j = 0; j < firstFrame.length; j++) {
                                                            byteListTwo.add(firstFrame[j]);
                                                        }
                                                    }
                                                    if (i == 3) {
                                                        String cardKey = entity.getCardId() + "_1" + entity.getOccurTime();
                                                        // 储存该帧上报的次数
                                                        udpMapData.alarmFrameNumerMapPut(cardKey);
                                                        byte[] bufferAll = new byte[byteListTwo.size()];
                                                        // 将值储存到临时变量
                                                        for (int j = 0; j < byteListTwo.size(); j++) {
                                                            bufferAll[j] = byteListTwo.get(j);
                                                        }
                                                        // 报警帧次数
                                                        String frameKey = String.valueOf(entity.getFrameId());
                                                        // 将值放入操作变量中
                                                        udpMapData.alarmUdpMapPut(cardKey, frameKey, bufferAll);
                                                    }
                                                }
                                            }
                                        } else { // 单通道逻辑
                                        	log.info("--- 单通道报警 ---");
                                            // 采集卡和通道组合Key(储存到内存中的时候使用)
                                            String cardKey = entity.getCardId() + "_" + entity.getChannelId() + entity.getOccurTime();
                                            // 储存该帧上报的次数
                                            udpMapData.alarmFrameNumerMapPut(cardKey);
                                            // 报警帧次数
                                            String frameKey = String.valueOf(entity.getFrameId());

                                            // 取出数据
                                            byte[] frameByte = entity.getData();
                                            ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
                                            byteBuf.writeBytes(frameByte);

                                            // 跳过12个字节，这12个字节在手持设备使用在此无用
                                            byteBuf.readBytes(12);

                                            List<Byte> byteList = new ArrayList<>();

                                            for (int j = 0; j < 2; j++) {
                                                byteBuf.skipBytes(4);
                                                byte[] byteLen = new byte[4];
                                                byteBuf.readBytes(byteLen);
                                                int len = ByteUtil.byteArrayToInt(byteLen);
                                                byte[] firstFrame = new byte[len];
                                                byteBuf.readBytes(firstFrame);

                                                // 将解压后的值放入集合中
                                                for (int i = 0; i < firstFrame.length; i++) {
                                                    byteList.add(firstFrame[i]);
                                                }

                                            }

                                            byte[] bufferAll = new byte[byteList.size()];
                                            // 将值储存到临时变量
                                            for (int i = 0; i < byteList.size(); i++) {
                                                bufferAll[i] = byteList.get(i);
                                            }
                                            // 将值放入操作变量中
                                            udpMapData.alarmUdpMapPut(cardKey, frameKey, bufferAll);

                                            ReferenceCountUtil.release(byteBuf);

                                        }
                                    } else if (entity.getCmd() == Constant.CMD_FRONT_ALARM_END) { // 报警结束帧处理逻辑
                                        // 帧序号为“2”说明是双通道结束
                                        if (channelId == 80) {
                                        	log.info("--- 双通道结束 ---");
                                            for (int i = 0; i < 2; i++) {
                                                // 采集卡和通道组合Key
                                                String cardKey = entity.getCardId() + "_" + i + entity.getOccurTime();
                                                // 处理完成对帧次数进行置空处理
                                                udpMapData.alarmFrameNumerSetNull(cardKey);
                                                // 调用储存及识别接口
                                                timingAudioService.recognitionAndpersistence(cardKey, String.valueOf(entity.getCardId()), String.valueOf(i),entity.getCardType(), 1, String.valueOf(entity.getOccurTime()));
                                            }
                                        } else {
                                        	log.info("--- 单通道结束 ---");
                                            // 采集卡和通道组合Key
                                            String cardKey = entity.getCardId() + "_" + entity.getChannelId() + entity.getOccurTime();
                                            // 判断上传的包数是否足够
                                            if (udpMapData.alarmFrameNumerMapGet(cardKey) >= Constant.ALARM_MIN_FRAMENUMER) {
                                                // 处理完成对帧次数进行置空处理
                                                udpMapData.alarmFrameNumerSetNull(cardKey);

                                                // 判断数据是否重复
                                                boolean repeatData = timingAudioService.dataIsAdd(entity.getCardId() + "_" + entity.getChannelId(), String.valueOf(entity.getOccurTime()), 2);

                                                if (repeatData) {
                                                    // 调用储存及识别接口
                                                    timingAudioService.recognitionAndpersistence(cardKey, String.valueOf(entity.getCardId()), String.valueOf(entity.getChannelId()), entity.getCardType(), 1, String.valueOf(entity.getOccurTime()));
                                                }

                                                // 收到结束帧后给嵌入式回复
                                                MessageEntity rtnMsg = new MessageEntity();
                                                rtnMsg.setCardId(entity.getCardId());
                                                rtnMsg.setChannelId(entity.getChannelId());
                                                byte cmd = 0x0F;
                                                rtnMsg.setVersionNum(entity.getVersionNum());
                                                rtnMsg.setCmd(cmd);
                                                // 帧序号
                                                rtnMsg.setFrameId(entity.getFrameId());
                                                // 数据总长度
                                                rtnMsg.setAllLen(23);
                                                // 类型
                                                rtnMsg.setCardType(entity.getCardType());
                                                // UTC时间
                                                // 转换成时间戳
                                                int utcTimeStamp = DateUtil.transForMilliSecond(new Date());
                                                rtnMsg.setOccurTime(utcTimeStamp);

                                                byte[] rtnByteMsg = MessageUtil.rtnMessageTobyte(rtnMsg);

                                                Channel channel = SocketChannelMap.get(String.valueOf(entity.getCardId()));

                                                channel.writeAndFlush(Unpooled.copiedBuffer(rtnByteMsg));

                                                log.info("--- 报警结束回复 ---");

                                            } else {
                                                // 获得一下数据以清空内存
                                                udpMapData.alarmUdpMapGet(cardKey);

                                                log.info("--- 报警数据，总包数未超过 95% ---");
                                            }

                                        }

                                    }
                                }

                            }

                        } else {
                            ReferenceCountUtil.release(buf);
                            log.info("--- UDP crc校验未通过 ---");
                        }

                    }
                }

            }
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
            //ctx.close();
        }

    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    	log.error("error [UdpServerHandler] exceptionCaught: {}", cause.getMessage());
    	ctx.close();
    }
}
